package com.demo.snakegame;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.JPanel;

public class MainScreen extends JPanel
						implements Runnable{
	
	public final int GAME_SPPED = 250; //속도를 조절하는 interval값 설정합니다.
	public final int SELL_WIDTH = 32; //가로 
	public final int SELL_HEIGHT = 32; //세로
	public final int HORIZONTAL_CELL_COUNT = 20;//수평 방향 셀확인
	public final int VERTICAL_CELL_COUNT = 20; //수직 방향 셀확인
	public final int INITIAL_SNAKE_SIZE = 3; // 초기 뱀크기
	public final int DIRECT_LEFT = 1;
	public final int DIRECT_RIGHT = 2;
	public final int DIRECT_UP = 3;
	public final int DIRECT_DOWN = 4;
	public Clip bgm,bite;
	public int score=0;
	public Font gameoverFont,scoreFont;
	
	public final int STATUS_INIT = 1;
	public int STATUS_RUNNING = 2;
	public final int STATUS_PAUSE = 3;
	public final int STATUS_CLEAR = 4;
	public final int STATUS_FAIL = 5;
		
	//머리 + 꼬리*n개 위치를 관리할 객체(좌표는 픽셀좌표가 아님 -> 20*20의 가상 셀좌표입니다 )
	public ArrayList<Point> Snake;
	//개구리 위치를 관리할 객체
	public Point frog;
	public int direction = DIRECT_RIGHT; //뱀의 이동 방향을 저장하는 변수
	
	public int status = STATUS_RUNNING;//현재 게임의 상태를 저장(시작전 게임중 게임종료(성공,실패))
	
	private Image headImage ,bodyImage, frogImage;//뱀머리 뱀몸통 개구리
	
	public MainScreen(){
		try{//이미지를 만드는작업도 예외가 발생될수있기에 try~catchㅊㅓ맇!
			
			//이미지가져옵니당~
			Image originalImage = ImageIO.read(new File("snakehead.png"));
			//뱀머리 크기를 맞춰주기위해 getScaledInstance메서드 가져옴
			headImage = originalImage.getScaledInstance(SELL_WIDTH, SELL_HEIGHT, Image.SCALE_SMOOTH);
			
			//이미지가져옵니당~
			originalImage = ImageIO.read(new File("body.png"));
			//뱀몸통 크기를 맞춰주기위해 getScaledInstance메서드 가져옴
			bodyImage = originalImage.getScaledInstance(SELL_WIDTH, SELL_HEIGHT, Image.SCALE_SMOOTH);
			
			//이미지가져옵니당~
			originalImage = ImageIO.read(new File("1.png"));
			//개구리 크기를 맞춰주기위해 getScaledInstance메서드 가져옴
			frogImage = originalImage.getScaledInstance(SELL_WIDTH, SELL_HEIGHT, Image.SCALE_SMOOTH);
		
			URL ur = getClass().getResource("/FOREST.wav");
			AudioInputStream ais = AudioSystem.getAudioInputStream(ur);//오디오 파일에 대한 스트림
			bgm = AudioSystem.getClip(); // 오디오 재생기 생성~~!!
			bgm.open(ais);//오디오 재생기에 오디오 음원을 삽입합니다~~
			bgm.loop(Clip.LOOP_CONTINUOUSLY);//반복 횟수를 지정합니다!!!!(LOOP_CONTINUOUSLy:무한반복)
			bgm.stop();
			//1넣어주면 한번만돌리는겨
			
			ur = getClass().getResource("/Bite.wav");
			ais = AudioSystem.getAudioInputStream(ur);//오디오 파일에 대한 스트림
			bite = AudioSystem.getClip(); // 오디오 재생기 생성~~!!
			bite.open(ais);//오디오 재생기에 오디오 음원을 삽입합니다~~
			bite.loop(1);//반복 횟수를 지정합니다!!!!(LOOP_CONTINUOUSLy:무한반복)
			bite.stop();
			//1넣어주면 한번만돌리는겨
			
			gameoverFont = new Font("Verdana", Font.BOLD, 15);//name style size
			scoreFont = new Font("Verdana", Font.BOLD, 12);//
			
	
		}catch(Exception e){
			e.printStackTrace(); //오류확인할라고 매번쓰는거임 printStackTrace~~!
		}
		
		setBackground(Color.black);
		
		//초기화 작업 뱀 개구리 키값
		Snake = new ArrayList<Point>(); 
		frog = new Point();
		
		addKeyListener(new KeyEventHandler());
		
		startGame();
		
		bgm.start();
		
	}
	
	/**
	 * 게임 관련 데이터를 초기화하고 게임을 시작하는 메서드
	 */
	public void startGame(){
		//기존에 구성된 뱀 내용을 제거
		if(Snake.size()>0)
		Snake.clear();//하위 요소 전부 삭제
		
		//뱀의 위치
		for(int i=0;i<INITIAL_SNAKE_SIZE;i++){
			Point point = new Point();
			point.x = INITIAL_SNAKE_SIZE+1 -i;
			point.y = 1;
			Snake.add(point);
		}
		
		//개구리의 위치
		frog = new Point();
		frog.x = (int)(Math.random()*HORIZONTAL_CELL_COUNT);
		frog.y = (int)((Math.random()*VERTICAL_CELL_COUNT/2)+VERTICAL_CELL_COUNT/2);
		//첨부터 뱀과 겹치지않게 10을 더해줍니다
		
		//frame loop 시작
		Thread thread = new Thread(this);
		thread.start(); // 0.25초당 한번씩 프레임 펌핑됨
	}
	
	//아래는 이벤트 처리 구현영역입니다
	
	//기본적인 구조 run메서드안에 update(데이터갱신)와 rander(그리기)를 처리해주고
	//rander는 paintComponent 메서드를 호출
	//키보드를 수신해주는 키보드이벤트핸들러를 만들어준다 (keyPressed)~
	
	//내부클래스
	//KeyListener를 구현하는 경우 인터페이스의 모든 메서드를 구현
//	class KeyEnventHandler implements KeyListener{
//	}
	/**
	 * 게임 요소 및 게임 관련 데이터의 변경을 처리하는 메서드
	 */
	public void update(){
		// Snake 몸통의 위치 변환
//		for(int i=Snake.size()-1;i>0;i--){
//			Snake.get(i).x = Snake.get(i-1).x;
//			Snake.get(i).y = Snake.get(i-1).y;
//		}
		// Snake 몸통의 위치 변환1
		Point last = Snake.remove(Snake.size()-1);
		int lastX = last.x; //뒤에 나오는 개구리 충돌 체크에서 새로 생성될 몸체의 좌표
		int lastY = last.y; //뒤에 나오는 개구리 충돌 체크에서 새로 생설될 몸체의 좌표
		last.x = Snake.get(0).x;
		last.y = Snake.get(0).y;
		Snake.add(1,last);
		
		// Snake 머리의 위치 변환
		switch(direction){
			case DIRECT_LEFT: 
					Snake.get(0).x--;
//					if(Snake.get(0).x <0) 뱀이 나갈경우 반대반향으로 돌려줌
//						 Snake.get(0).x = HORIZONTAL_CELL_COUNT;
					break;
			case DIRECT_RIGHT: 
					Snake.get(0).x++;
					break;
			case DIRECT_UP: 
					Snake.get(0).y--;
					break;
			case DIRECT_DOWN:
					Snake.get(0).y++;
					break;
		}	
		
		//개구리와 뱀의 머리가 충돌(같은위치)인지 확인하고 개구리 위치변경합니다.
		do{
			if(Snake.get(0).x == frog.x && Snake.get(0).y == frog.y){
					frog.setLocation((int)(Math.random()*20),
							(int)(Math.random()*20));
					bite.setFramePosition(0);
					bite.start();
					
				
					score = score +10;
					//개구리 몸체를 확장
					Point newBody = new Point(last.x,last.y);
					Snake.add(newBody);
			}
		}while(Snake.get(0).x == frog.x && Snake.get(0).y == frog.y);
		
		//뱀과 벽이 충돌하는 경우 (종료 조건으로 처리하는 경우)
		if( Snake.get(0).x < 0 || Snake.get(0).x>HORIZONTAL_CELL_COUNT-1 ||
			Snake.get(0).y < 0 || Snake.get(0).y>VERTICAL_CELL_COUNT-1){
			status = STATUS_FAIL;
				//게임 오버
			}
		
		//뱀과 몸체과 충돌하는 경우(종료 조건)
		for(int i=4; i<Snake.size(); i++){
			if(Snake.get(i).x == Snake.get(0).x && 
			   Snake.get(i).y == Snake.get(0).y){
				status = STATUS_FAIL;
				//게임 오버
				break;
			}
			
		}
	}
	//그리기 작업
	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		
		Graphics2D g2D = (Graphics2D)g;
		
		g2D.drawImage(frogImage, 
				frog.x*SELL_WIDTH,frog.y*SELL_HEIGHT, //pixel 좌표를 산출
				null);
		
		 for(int i=0; i<Snake.size();i++){
			 Image image = null;
			 if(i == 0) 
				 image = headImage;
			 else
				 image = bodyImage;
			 
			 g2D.drawImage(image, Snake.get(i).x*SELL_WIDTH,
					 				Snake.get(i).y*SELL_HEIGHT,null);
		 }
		 
		 //점수 출력(테스트)
		 g2D.setFont(scoreFont);
		 g2D.setColor(Color.red);
		 g2D.drawString("SCORE :"+score,500,50);//name x y;
		 
		 
		 if(status == STATUS_FAIL){
			 g2D.setFont(gameoverFont);//폰트설정!!!
			 g2D.setColor(Color.orange);
			 //글씨의 여백을 정해주기위한 rect
			 Rectangle2D rect = 
					 g2D.getFontMetrics().getStringBounds("GAME OVER!!!", g2D);
			 rect = 
					 g2D.getFontMetrics().getStringBounds("SCORE : 0000", g2D);
			 int x =(Constants.SCREEN_WIDTH - (int)rect.getWidth())/2;
			 int y =(Constants.SCREEN_WIDTH - (int)rect.getWidth())/2;
			 g2D.drawString("GAME OVER!!!", x, y);
			 g2D.drawString("SCORE : "+score, x, y+30);
			 
		 }
	}
	@Override
	public void run() {
		while(true){
			if(status == STATUS_RUNNING){//스테이트가 런닝이라면 게임실행
				//data 변경하는 작업을 수행!!
				update();
			}else if(status == STATUS_FAIL){
//				STATUS_RUNNING = 2;
//				update();
			}
			//render(그리는) 작업을 수행!!!
			repaint();//-> 외부에서 페인트를 호출하게됩니당당구루루루당당수구구리당당
			
			try{//당연히 속도처리에서 예외발생할수있기에 try~catch처리
				Thread.sleep(GAME_SPPED); //이동을 제한을 두기위해 sleep을 걸어놨숨당..(프레임용 속도)
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	//KeyAdapter : KeyListener를 미리 구현한 클래스
	//			   이 클래스를 상속하면 이벤트를 수신하면서 필요한 메서드만 재정의 가능합니다~
	class KeyEventHandler extends KeyAdapter{
		@Override
		public void keyPressed(KeyEvent e) {
			// TODO Auto-generated method stub
			super.keyPressed(e);
			
			switch(e.getKeyCode()){
			case KeyEvent.VK_LEFT:
				if(direction != DIRECT_RIGHT) //오른쪽으로 가지않았을떄만
				direction = DIRECT_LEFT;
				break;
			case KeyEvent.VK_RIGHT:
				if(direction != DIRECT_LEFT) //왼쪽으로 가지않았을때만
				direction = DIRECT_RIGHT;
				break;
			case KeyEvent.VK_UP:
				if(direction != DIRECT_DOWN) //밑으로 가지않았을때만
				direction = DIRECT_UP;
				break;
			case KeyEvent.VK_DOWN:
				if(direction != DIRECT_UP) //위로 가지않았을때만
				direction = DIRECT_DOWN;
				break;
			}
			
		}
	}


}
